
package com.jf.cloud.seckill.service.impl;

import com.jf.cloud.api.order.feign.OrderFeignClient;
import com.jf.cloud.api.product.feign.SpuFeignClient;
import com.jf.cloud.common.cache.constant.CacheNames;
import com.jf.cloud.common.cache.constant.LockCacheNames;
import com.jf.cloud.common.cache.constant.SeckillCacheNames;
import com.jf.cloud.common.cache.util.RedisUtil;
import com.jf.cloud.common.constant.Constant;
import com.jf.cloud.common.exception.LuckException;
import com.jf.cloud.common.order.constant.OrderActivityType;
import com.jf.cloud.common.order.vo.OrderActivityVO;
import com.jf.cloud.common.order.vo.ShopCartItemVO;
import com.jf.cloud.common.order.vo.ShopCartOrderMergerVO;
import com.jf.cloud.common.order.vo.ShopCartOrderVO;
import com.jf.cloud.common.response.ServerResponseEntity;
import com.jf.cloud.seckill.constant.RedisLua;
import com.jf.cloud.seckill.mapper.SeckillMapper;
import com.jf.cloud.seckill.mapper.SeckillSkuMapper;
import com.jf.cloud.seckill.service.SeckillOrderService;
import com.jf.cloud.seckill.service.SeckillSkuService;
import com.jf.cloud.seckill.vo.SeckillSkuVO;
import com.jf.cloud.seckill.vo.SeckillVO;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Objects;
import java.util.Random;

/**
 * 秒杀订单
 *
 * @author zz
 * @date 2021-03-30 14:59:28
 */
@Service
public class SeckillOrderServiceImpl implements SeckillOrderService {

    @Autowired
    private SeckillSkuMapper seckillSkuMapper;
    @Autowired
    private SeckillMapper seckillMapper;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private SeckillSkuService seckillSkuService;

    @Autowired
    private SpuFeignClient spuFeignClient;

    @Autowired
    private OrderFeignClient orderFeignClient;


    @Override
    public boolean checkOrderSpuNum(SeckillVO seckill, Long userId, Integer count) {
        // 判断之前秒杀的商品有没有超过限制，-1表示商品不限制秒杀数量
        if (!Objects.equals(seckill.getMaxNum(), -1)) {
            ServerResponseEntity<Integer> serverResponseEntity = orderFeignClient.getUserBuyNumByActivityId(seckill.getSeckillId(), OrderActivityType.SECKILL.value(), seckill.getSpuId(), userId);
            if (serverResponseEntity.isFail()) {
                throw new LuckException(serverResponseEntity.getMsg());
            }
            // 本次秒杀商品限购
            return seckill.getMaxNum() >= count + serverResponseEntity.getData();
        }
        return true;
    }

    @Override
    public void submit(ShopCartOrderMergerVO mergerOrder) {
        ShopCartOrderVO shopCartOrderVO = mergerOrder.getShopCartOrders().get(0);
        ShopCartItemVO shopCartItemVO = shopCartOrderVO.getShopCartItemDiscounts().get(0).getShopCartItems().get(0);

        // 更新秒杀sku库存
        decrSeckillSkuStocks(mergerOrder.getSeckillSkuId(), shopCartItemVO.getCount(),shopCartItemVO.getSkuId(),mergerOrder.getActivityId());
    }

    @Override
    public boolean orderSaveSuccess(Long orderId) {
        ServerResponseEntity<OrderActivityVO> activityOrderInfo = orderFeignClient.getActivityOrderInfo(orderId);
        if (activityOrderInfo.isFail() || Objects.isNull(activityOrderInfo.getData())) {
            return false;
        }
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void cancelUnpayOrderByOrderId(Long orderId) {
        ServerResponseEntity<OrderActivityVO> activityOrderPayInfo = orderFeignClient.getActivityOrderInfo(orderId);
        if (activityOrderPayInfo.isFail()) {
            throw new LuckException(activityOrderPayInfo.getMsg());
        }
        OrderActivityVO order = activityOrderPayInfo.getData();
        if (Objects.isNull(order)) {
            return;
        }
        if(Objects.equals(order.getIsPayed(), 1)) {
            return;
        }
        // 还原秒杀库存
        SeckillSkuVO seckillSku = seckillSkuMapper.getBySkuIdAndSeckillId(order.getActivityId(), order.getSkuId());
        if (Objects.isNull(seckillSku)) {
            throw new LuckException("秒杀订单的sku信息不存在--订单id为:"+ order.getOrderId() + "-活动id为：" + order.getActivityId());
        }
        seckillSkuMapper.returnStocksByOrderId(seckillSku.getSeckillSkuId(), order.getCount());
        // 还原秒杀库存
        seckillMapper.returnStocksByOrderId(order.getActivityId(), order.getCount());

        RLock rLock = redissonClient.getLock(LockCacheNames.LOCK_SECKILL_ZONE_PERFIX + seckillSku.getSeckillSkuId());
        try {
            rLock.lock();
            Integer availableZone = seckillSkuService.getAvailableZone(seckillSku.getSeckillSkuId(),seckillSku.getSkuId(),seckillSku.getSeckillId());
            if (availableZone == 0) {
                // 库存回退了，所以有可用的库存分片了
                RedisUtil.set(SeckillCacheNames.SECKILL_SKU_AVAILABLE_ZONE + seckillSku.getSeckillSkuId(), 1, -1);
                availableZone = 1;
            }
            // 随机对一个分片增加库存
            Random random = new Random();
            int zoneNo = random.nextInt(availableZone);

            // 库存回退到第一个分片里面
            RedisUtil.incr(SeckillCacheNames.SECKILL_SKU_STOCK + seckillSku.getSeckillSkuId() + CacheNames.UNION_KEY + zoneNo, order.getCount());
        } finally {
            rLock.unlock();
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void paySuccessOrderByOrderId(Long orderId) {
        ServerResponseEntity<OrderActivityVO> activityOrderPayInfo = orderFeignClient.getActivityOrderInfo(orderId);
        if (activityOrderPayInfo.isFail()) {
            throw new LuckException(activityOrderPayInfo.getMsg());
        }
        OrderActivityVO order = activityOrderPayInfo.getData();
        if (Objects.isNull(order)) {
            return;
        }
        if(Objects.equals(order.getIsPayed(), 0)){
            return;
        }
        // 秒杀订单支付成功后，增加销量
        spuFeignClient.updateSaleNumBySeckillOrder(order.getSpuId(), order.getCount());
    }

    private void decrSeckillSkuStocks(Long seckillSkuId, Integer prodCount, Long skuId, Long seckillId) {
        RLock rLock = redissonClient.getLock(LockCacheNames.LOCK_SECKILL_ZONE_PERFIX + seckillSkuId);
        try {
            rLock.lock();
            Integer availableZone = seckillSkuService.getAvailableZone(seckillSkuId,skuId,seckillId);

            if (availableZone == 0) {
                throw new LuckException("库存不足");
            }

            // 1.通过分片扣减，扣减成功 不加锁，因为绝大部分情况下都是成功的
            if (reduceStockByZone(seckillSkuId, prodCount, availableZone,seckillId)) {
                return;
            }

            // 2.次分片无库存或扣减失败，聚合所有分片扣减库存
            // 2.1 统计所有库存数量还剩多少
            Integer totalStock = seckillSkuService.getStockBySeckillSkuId(seckillSkuId);

            // 2.2 库存不够扣减了
            if (totalStock < prodCount) {
                throw new LuckException("库存不足");
            }

            // 2.3 剩余库存重新分片
            int lastStock = totalStock - prodCount;
            seckillSkuService.doZone(seckillSkuId, lastStock);
        } finally {
            rLock.unlock();
        }
    }



    /**
     * 通过分片扣减库存，如果扣减失败就聚合所有分片扣减
     *
     * @param seckillSkuId  秒杀skuId
     * @param prodCount     扣减库存数
     * @param availableZone 可用分片数量
     * @param seckillId
     * @return 是否扣减成功
     */
    public boolean reduceStockByZone(Long seckillSkuId, Integer prodCount, int availableZone, Long seckillId) {
        // 先对随机单个分片进行扣减
        Random random = new Random();
        int zoneNo = random.nextInt(availableZone);

        Long hasStock = RedisUtil.eval(RedisLua.REDUCE_STOCK_MUST_ENOUGH_SCRIPT,
                SeckillCacheNames.SECKILL_SKU_STOCK + seckillSkuId + CacheNames.UNION_KEY + zoneNo, prodCount.toString(), Long.class);
        // 库存每剩余100件就更新一遍库存，则代表什么意思呢？
        // 理论上构想每卖出100件就落库一次，因为实际上一般秒杀都是以一件一件的抢购的，为了秒杀的性能，我们就认为每次剩余库存还剩100的倍数，下一次也是差不多剩余100的倍数。
        // 所以这个时候在理想状态下达到了每卖出100次更新一遍库存。
        // 但也会出现一个问题，没有一次达到100，就没办法落地了么可以通过定时任务的形式进行补充。
        if (prodCount> Constant.HUNDRED || hasStock % Constant.HUNDRED == 0) {
            seckillSkuService.flushStockToDb(seckillSkuId, seckillId);
        }

        if (hasStock == 0) {
            // 可用库存分片数量减一
            RedisUtil.decr(SeckillCacheNames.SECKILL_SKU_AVAILABLE_ZONE + seckillSkuId, 1);
        }
        return hasStock >= 0;
    }

}
